package server

import (
	"errors"
	"io"
	"log/slog"
	"net"
	"os"
	"os/signal"
	"strings"
	"sync"
	"syscall"

	"lsp-server/internal/protocol"
	"lsp-server/internal/server/handlers"
)

type Server struct {
	listener          net.Listener
	quit              chan interface{}
	wg                sync.WaitGroup
	mux               *Mux
	fileURIToContents *map[string][]string
}

func NewServer() *Server {
	s := &Server{
		quit:              make(chan interface{}),
		mux:               NewMux(),
		fileURIToContents: &map[string][]string{},
	}
	return s
}

const docChangesBuffSize = 10

func (s *Server) serve() {
	defer s.wg.Done()

	documentUpdates := make(chan protocol.TextDocumentItem, docChangesBuffSize)
	s.registerHandlers(documentUpdates)
	go s.runDiagnostic(documentUpdates)

	for {
		conn, err := s.listener.Accept()
		if err != nil {
			select {
			case <-s.quit:
				return
			default:
				slog.Error("accept error", err)
			}
		} else {
			s.updateConn(&conn)
			s.wg.Add(1)
			go func() {
				s.handleConnection(conn)
				s.wg.Done()
			}()
		}
	}
}

func (s *Server) updateConn(conn *net.Conn) {
	s.mux.NewConn(conn)
}

func (s *Server) Stop() {
	close(s.quit)
	s.listener.Close()
	s.wg.Wait()
}

func (s *Server) handleConnection(conn net.Conn) {
	defer conn.Close()

	slog.Info("received from %v", conn.RemoteAddr())

	err := s.mux.Process()
	if err != nil && errors.Is(err, io.EOF) {
		slog.Error("read error, err: %v", err)
		return
	}
}

func (s *Server) Run(port string) {
	l, err := net.Listen("tcp", ":"+port)
	if err != nil {
		slog.Error(err.Error())
		return
	}
	s.listener = l
	s.wg.Add(1)
	go s.serve()

	quit := make(chan os.Signal, 1)
	signal.Notify(quit, os.Interrupt, syscall.SIGTERM, syscall.SIGTSTP)

	<-quit

	s.Stop()
}

func (s *Server) registerHandlers(documentUpdates chan protocol.TextDocumentItem) {
	s.mux.HandleRequest(handlers.InitializeMethod, handlers.Initialize{})
	s.mux.HandleNotification(handlers.InitializedNotification, handlers.Initialized{})

	s.mux.HandleRequest(handlers.HoverMethod, handlers.NewHover(s.fileURIToContents))
	s.mux.HandleNotification(handlers.DidOpenTextDocumentNotification, handlers.NewDidOpen(documentUpdates))
	s.mux.HandleNotification(handlers.DidChangeTextDocumentNotification, handlers.NewDidChange(documentUpdates))
	s.mux.HandleRequest(handlers.CompletionRequestMethod, handlers.NewCompletion(s.fileURIToContents))
}

func (s *Server) runDiagnostic(documentUpdates chan protocol.TextDocumentItem) {
	for doc := range documentUpdates {
		diagnostics := s.createDiagnostics(doc)

		err := s.mux.Notify(
			handlers.PublishDiagnosticsMethod,
			protocol.PublishDiagnosticsParams{
				URI:         doc.URI,
				Version:     doc.Version,
				Diagnostics: diagnostics,
			})
		if err != nil {
			slog.Error("error to send diagnostic notify", slog.Any("err", err))
			return
		}
	}
}

func (s *Server) createDiagnostics(doc protocol.TextDocumentItem) []protocol.Diagnostic {
	docSplit := strings.Split(doc.Text, "\n")
	(*s.fileURIToContents)[doc.URI.Path()] = docSplit
	diagnostics := []protocol.Diagnostic{}
	diagnostics = append(diagnostics, protocol.Diagnostic{
		Severity: protocol.SeverityError,
		Message:  "some diagnostic",
		Source:   "lsp-server",
		Range: protocol.Range{
			Start: protocol.Position{
				Line:      0,
				Character: 0,
			},
			End: protocol.Position{
				Line:      0,
				Character: 0,
			},
		},
	})
	return diagnostics
}
